package com.coman404.mvc.conf;

import com.coman404.mvc.annotation.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.*;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.*;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author NotFound404 at 2024/6/3
 * code code always code
 */

@Configuration
public class ControllerHandlerConf {

    @Autowired
    RequestDispatch requestDispatch;

    private Gson gson = new GsonBuilder().create();

    @Bean
    public ConversionService conversionService() {
        return new DefaultConversionService();
    }

    @Bean
    public BeanPostProcessor handlerAnnotationPostProcessor() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
            }

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean.getClass().isAnnotationPresent(RestController.class) || bean.getClass().isAnnotationPresent(Controller.class)){
                    if (!bean.getClass().isAnnotationPresent(RequestMapping.class)){
                        throw new Error("Controller must have RequestMapping");
                    }
                    RequestMapping requestMapping = AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
                    String basePath = requestMapping.value();
                    ReflectionUtils.doWithMethods(bean.getClass(), method -> {
                        RequestMapping methodMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
                        if (methodMapping != null){
                            String methodPath = methodMapping.value();
                            String path = basePath + methodPath;
                            String requestMethod = methodMapping.method();
                            ControllerMappingHolder holder = new ControllerMappingHolder();
                            holder.setControllerName(beanName);
                            holder.setMethodName(method.getName());
                            holder.setPath(path);
                            holder.setMethodType(requestMethod);
                            if (requestMethod.equals("GET")){
                                holder.setHandler(getHandler(bean,method));
                            }else if (requestMethod.equals("POST")){
                                holder.setHandler(postHandler(bean,method));
                            }
                            requestDispatch.addHolder(holder);
                        }
                    }, method -> method.isAnnotationPresent(RequestMapping.class));
                }
                return bean;
            }
        };
    }


    public ControllerMappingHolder.Handler getHandler(Object bean,Method method){
        return (request, response) -> {
            String uri = request.uri();
            QueryStringDecoder decoder = new QueryStringDecoder(uri);
            Map<String,Object> parmMap = new HashMap<>();
            decoder.parameters().entrySet().forEach( entry -> {
                parmMap.put(entry.getKey(), entry.getValue().get(0));
            });
            int paramCount = method.getParameterCount();
            Object result = null;
            if (paramCount > 0){
                Object[] params = new Object[method.getParameterCount()];
                for(int i=0;i<paramCount;i++){
                    Parameter parameter = method.getParameters()[i];
                    RequestParam param = parameter.getAnnotation(RequestParam.class);
                    Object val = conversionService().convert(parmMap.get(param.value()),parameter.getType());
                    params[i] = val;
                }
                result = ReflectionUtils.invokeMethod(method, bean, params);
            }else{
                result = ReflectionUtils.invokeMethod(method, bean);
            }
            response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
            return response.replace(Unpooled.copiedBuffer(gson.toJson(result).getBytes(StandardCharsets.UTF_8)));
        };
    }

    public ControllerMappingHolder.Handler postHandler(Object bean,Method method){
        return (request, response) -> {
            HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(),request);
//            decoder.setDiscardThreshold(0);
            String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
            Map<String, String> parmMap = new HashMap<>();
            List<InterfaceHttpData> bodyHttpDatas = decoder.getBodyHttpDatas();
            for (InterfaceHttpData httpData:bodyHttpDatas){
                Attribute data = (Attribute) httpData;;
                parmMap.put(data.getName(), data.getValue());
            }
            String bodyStr = null;
            if (contentType.contains("application/json")){
                request.content().resetReaderIndex();
                request.content().resetReaderIndex();
                ByteBuf  buf = request.content();
                byte[] buffer = new byte[buf.readableBytes()];
                buf.readBytes(buffer);
                bodyStr = new String(buffer, StandardCharsets.UTF_8);
            }
            decoder.destroy();
            int paramCount = method.getParameterCount();
            Object result = null;
            if (paramCount > 0){
                Object[] params = new Object[method.getParameterCount()];
                for(int i=0;i<paramCount;i++){
                    Parameter parameter = method.getParameters()[i];
                    RequestBody body = parameter.getAnnotation(RequestBody.class);
                    if (Objects.nonNull(body) && Objects.nonNull(bodyStr)){
                        params[i] = gson.fromJson(bodyStr,TypeToken.get(parameter.getType()).getType());
                    }else{
                        RequestParam param = parameter.getAnnotation(RequestParam.class);
                        if (Objects.nonNull(param)){
                            Object val = conversionService().convert(parmMap.get(param.value()),parameter.getType());
                            params[i] = val;
                        }
                    }
                }
                result = ReflectionUtils.invokeMethod(method, bean, params);
            }else{
                result = ReflectionUtils.invokeMethod(method, bean);
            }
            response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
            return response.replace(Unpooled.copiedBuffer(gson.toJson(result).getBytes(StandardCharsets.UTF_8)));
        };
    }


}
